/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.kurvykurvy.image;

import com.kurvykurvy.ui.*;
import com.kurvykurvy.page.ImageFrame;
import com.kurvykurvy.page.ImageFrameFactory;
import com.kurvykurvy.page.ImageFrameTemplate;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.TransferHandler;
import javax.swing.event.MouseInputListener;
import org.openide.util.Exceptions;

/**
 *
 * @author HY
 */
public class ImageFrameComponent
    extends JComponent
    implements MouseInputListener, DropTargetListener
{
    protected final static int ROTATE_MODE = 1;
    protected final static int RESIZE_MODE = 2;
    protected final static int PAN_MODE = 4;

    protected final static int NORTHWEST = 1;
    protected final static int NORTH = 2;
    protected final static int NORTHEAST = 3;
    protected final static int WEST = 4;
    protected final static int EAST = 5;
    protected final static int SOUTHWEST = 6;
    protected final static int SOUTH = 7;
    protected final static int SOUTHEAST = 8;

    protected final static int TOP_LEFT_RESIZE = 0;
    protected final static int TOP_RESIZE = 1;
    protected final static int TOP_RIGHT_RESIZE = 2;
    protected final static int LEFT_RESIZE = 3;
    protected final static int RIGHT_RESIZE = 4;
    protected final static int BOTTOM_LEFT_RESIZE = 5;
    protected final static int BOTTOM_RESIZE = 6;
    protected final static int BOTTOM_RIGHT_RESIZE = 7;

    private boolean mouseIn = false;
    private boolean trackMovement = false;
    private boolean snapToFrame = true;
    
    ImageFrame imageFrame;
    
    Point location = new Point( 0, 0 );
    Point locationOffset = new Point( 0, 0 );
    Point scalingOffset = new Point( 0, 0 );
    Point dragPoint = null;
    double rotationOffset = 0.0;
    double xScale = 1.0;
    double yScale = 1.0;
    int resizePoint = 0;
    int direction = 0;
    boolean altModifier = false;
    int modifyMode = 0;

    Point rotationPoint = null;
    Point mouseLocation = null;
    Point startPoint = null;

    Rectangle[] rotationSpots = new Rectangle[4];
    int rotationHotSpotSize = 10;
    int rotationHotSpotOffset = 10;

    Rectangle[] resizeSpots = new Rectangle[ 8 ];
    int resizeHotSpotSize = 10;
    int resizeHotSpotOffset = 0;

    AffineTransform transformComponent;

    ImageFrameOverlay overlay;

    private ImageFrameComponent()
    {
        addMouseMotionListener( this );
        addMouseListener( this );

        this.setTransferHandler( new LoadedImageExportTransferHandler() );
        this.setDropTarget(  new DropTarget( this, this) );
    }

    public ImageFrameComponent( LoadedImage img )
    {
        this();
        ImageFrameTemplate template = new ImageFrameTemplate( 0, 0, img.getImage().getWidth(), img.getImage().getHeight() );
        imageFrame = ImageFrameFactory.createImageFrame( template, img );
    }

    public ImageFrameComponent( BufferedImage img )
    {
        this();
        ImageFrameTemplate template = new ImageFrameTemplate( 0, 0, img.getWidth(), img.getHeight() );
        imageFrame = ImageFrameFactory.createImageFrame( template, img );
        setPreferredSize( new Dimension( imageFrame.getSize().width, imageFrame.getSize().height ) );
        setBounds( imageFrame.getOffset().x, imageFrame.getOffset().y, imageFrame.getSize().width, imageFrame.getSize().height );
        createRotationSpots();
        createResizeSpots();
    }

    public ImageFrameComponent( ImageFrame frame )
    {
        this();
        imageFrame = frame;
        setPreferredSize( new Dimension( imageFrame.getSize().width, imageFrame.getSize().height ) );
        setBounds( imageFrame.getOffset().x, imageFrame.getOffset().y, imageFrame.getSize().width, imageFrame.getSize().height );
        createRotationSpots();
        createResizeSpots();
    }

    private void createRotationSpots()
    {
        Dimension imageSize = imageFrame.getSize();
        rotationSpots[ 0 ] = new Rectangle( rotationHotSpotOffset, rotationHotSpotOffset, rotationHotSpotSize, rotationHotSpotSize );
        rotationSpots[ 1 ] = new Rectangle( imageSize.width - ( rotationHotSpotOffset + rotationHotSpotSize ), rotationHotSpotOffset, rotationHotSpotSize, rotationHotSpotSize );
        rotationSpots[ 2 ] = new Rectangle( rotationHotSpotOffset, imageSize.height - ( rotationHotSpotOffset + rotationHotSpotSize ), rotationHotSpotSize, rotationHotSpotSize );
        rotationSpots[ 3 ] = new Rectangle( imageSize.width - ( rotationHotSpotOffset + rotationHotSpotSize ), imageSize.height - ( rotationHotSpotOffset + rotationHotSpotSize ), rotationHotSpotSize, rotationHotSpotSize );
    }

    private void createResizeSpots()
    {
        Dimension imageSize = imageFrame.getSize();
        resizeSpots[ 0 ] = new Rectangle( resizeHotSpotOffset, resizeHotSpotOffset, resizeHotSpotSize, resizeHotSpotSize );
        resizeSpots[ 1 ] = new Rectangle( ( imageSize.width / 2 ) - resizeHotSpotSize, resizeHotSpotOffset, resizeHotSpotSize, resizeHotSpotSize );
        resizeSpots[ 2 ] = new Rectangle( imageSize.width - resizeHotSpotSize, resizeHotSpotOffset, resizeHotSpotSize, resizeHotSpotSize );
        resizeSpots[ 3 ] = new Rectangle( resizeHotSpotOffset, ( imageSize.height / 2 ) - resizeHotSpotSize, resizeHotSpotSize, resizeHotSpotSize );
        resizeSpots[ 4 ] = new Rectangle( imageSize.width - resizeHotSpotSize, ( imageSize.height / 2 ) - resizeHotSpotSize, resizeHotSpotSize, resizeHotSpotSize );
        resizeSpots[ 5 ] = new Rectangle( resizeHotSpotOffset, imageSize.height - resizeHotSpotSize, resizeHotSpotSize, resizeHotSpotSize );
        resizeSpots[ 6 ] = new Rectangle( ( imageSize.width / 2 ) - resizeHotSpotSize, imageSize.height - resizeHotSpotSize, resizeHotSpotSize, resizeHotSpotSize );
        resizeSpots[ 7 ] = new Rectangle( imageSize.width - resizeHotSpotSize, imageSize.height - resizeHotSpotSize, resizeHotSpotSize, resizeHotSpotSize );

    }

    public ImageFrameOverlay getImageFrameOverlay()
    {
        if( this.overlay == null )
            this.overlay = ImageFrameOverlay.getImageFrameOverlay( this );

        return this.overlay;
    }

    @Override
    public Rectangle getBounds()
    {
        Rectangle bounds = super.getBounds();
        return bounds;
    }

    public void computeTransformations()
    {

        Dimension imageSize = new Dimension( this.imageFrame.getImage().getWidth(), this.imageFrame.getImage().getHeight() );
        if( ( ROTATE_MODE & modifyMode ) == ROTATE_MODE )
        {
            double startRotation = 0;
            int adjustedX = ( imageSize.width / 2 ) - rotationPoint.x;
            int adjustedY = ( imageSize.height / 2 ) - rotationPoint.y;
            double rotationd = Math.atan2( adjustedY, adjustedX );
            if( startPoint != null )
            {
                startRotation = Math.atan2( ( imageSize.width / 2 ) - startPoint.x, ( imageSize.height / 2 ) - startPoint.y );
//                System.out.println( "Start Rotation : " + startRotation + "(" + ( startRotation * 360 / ( Math.PI * 2 ) ) + ")" );
            }
            if( Math.abs(  rotationd ) < 0.1 )
            {
                rotationd = 0;
            }
            rotationOffset = rotationd - startRotation;
//            System.out.println( "Rotation : " + rotationd + "(" + ( rotationd * 360 / ( Math.PI * 2 ) ) + ")" );
        }
        else if( ( RESIZE_MODE & modifyMode ) == RESIZE_MODE )
        {
            Point basePoint = new Point();
            Point endPoint = new Point();
            int xScalingModifier = 1;
            int yScalingModifier = 1;
            switch( resizePoint )
            {
                case TOP_LEFT_RESIZE:
                    xScalingModifier = 1;
                    yScalingModifier = 1;
                    basePoint = new Point( getBounds().width, getBounds().height );
                    endPoint = new Point( 0, 0 );
                    break;
                case TOP_RESIZE:
                    xScalingModifier = 0;
                    yScalingModifier = 1;
                    basePoint = new Point( getBounds().width/2, getBounds().height );
                    endPoint = new Point( getBounds().width/2, 0 );
                    break;
                case TOP_RIGHT_RESIZE:
                    xScalingModifier = 0;
                    yScalingModifier = 1;
                    basePoint = new Point( 0, getBounds().height );
                    endPoint = new Point( getBounds().width, 0 );
                    break;
                case LEFT_RESIZE:
                    xScalingModifier = 1;
                    yScalingModifier = 0;
                    basePoint = new Point( getBounds().width, getBounds().height/2 );
                    endPoint = new Point( 0, getBounds().height/2 );
                    break;
                case RIGHT_RESIZE:
                    xScalingModifier = 0;
                    yScalingModifier = 0;
                    basePoint = new Point( 0, getBounds().height/2 );
                    endPoint = new Point( getBounds().width, getBounds().height/2 );
                    break;
                case BOTTOM_LEFT_RESIZE:
                    xScalingModifier = 1;
                    yScalingModifier = -1;
                    basePoint = new Point( getBounds().width, 0 );
                    endPoint = new Point( 0, getBounds().height );
                    break;
                case BOTTOM_RESIZE:
                    xScalingModifier = 0;
                    yScalingModifier = 0;
                    basePoint = new Point( getBounds().width/2, 0 );
                    endPoint = new Point( getBounds().width/2, getBounds().height );
                    break;
                case BOTTOM_RIGHT_RESIZE:
                    xScalingModifier = 0;
                    yScalingModifier = 0;
                    basePoint = new Point( 0, 0 );
                    endPoint = new Point( getBounds().width, getBounds().width );
                    break;
            }


            double actualLength = Math.sqrt( Math.pow( (double)basePoint.x - (double)endPoint.x , 2d ) + Math.pow( ( (double)basePoint.y - (double)endPoint.y ), 2d ) );
            double scaledLength = Math.sqrt( Math.pow( (double)basePoint.x - (double)dragPoint.x, 2d ) + Math.pow( (double)basePoint.y - (double)dragPoint.y, 2d ) );
            double scale = scaledLength / actualLength;

            xScale = scale;
            yScale = scale;

            scalingOffset.x = xScalingModifier * (int) ( imageSize.width * ( 1 - scale ) );
            scalingOffset.y = yScalingModifier * (int) ( imageSize.height * ( 1- scale ) );

            System.out.println( scale );
            System.out.println( scalingOffset.x );
            System.out.println( scalingOffset.y );
        }
    }

    @Override
    public void paintComponent( Graphics g )
    {
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
        AffineTransform originalTransform = g2.getTransform();
        transformComponent = (AffineTransform) originalTransform.clone();
        Dimension imageSize = this.imageFrame.getSize();

        computeTransformations();
        transformComponent.rotate( rotationOffset, imageSize.width / 2, imageSize.height / 2 );
        transformComponent.scale( xScale, yScale );
        g2.setTransform( transformComponent );
        g2.drawImage( this.imageFrame.getImage(), location.x + locationOffset.x + scalingOffset.x, location.y + locationOffset.y + scalingOffset.y, null );
        g2.setTransform( originalTransform );

        if( mouseIn )
        {
            for( int i = 0; i < rotationSpots.length; i++ )
            {
                g2.setColor(  Color.BLUE );
                g2.draw( rotationSpots[ i ] );
            }

            for( int i = 0; i < resizeSpots.length; i ++ )
            {
                g2.setColor( Color.WHITE );
                g2.draw(  resizeSpots[ i ] );
            }
        }
    }

    public AffineTransform getTransformComponent()
    {
        return this.transformComponent;
    }

    public Dimension getFrameSize()
    {
        Dimension size = null;
        if( imageFrame != null )
        {
            size = this.imageFrame.getSize();
        }
        return size;
    }

    public BufferedImage getImage()
    {
        BufferedImage result = null;
        if( imageFrame != null )
        {
            result = this.imageFrame.getImage();
        }
        return result;
    }

    public void mouseClicked( MouseEvent e )
    {
    }

    public void mousePressed( MouseEvent e )
    {
    }

    public void mouseReleased( MouseEvent e )
    {
        this.startPoint = null;
        this.rotationPoint = null;
        this.dragPoint = null;
        modifyMode = 0;
        System.out.println( "Transformation Component: " );
        System.out.println( "Translation : " + transformComponent.getTranslateX() + " , " + transformComponent.getTranslateY() );
        System.out.println( "Scaling : " + transformComponent.getScaleX() + ", " + transformComponent.getScaleY() );
        this.imageFrame.applyTransformation( transformComponent );
        transformComponent = null;
        this.scalingOffset = new Point();
        this.rotationOffset = 0;
        this.xScale = 1.0;
        this.yScale = 1.0;
        this.location.x += locationOffset.x;
        if( snapToFrame && Math.abs( location.x ) < 5 )
        {
            this.location.x = 0;
        }

        this.location.y += locationOffset.y;
        if( snapToFrame && Math.abs( location.y ) < 5 )
        {
            this.location.y = 0;
        }
        this.locationOffset = new Point();
        this.repaint();
    }

    public void mouseEntered( MouseEvent e )
    {
        this.mouseIn = true;
        if( this.overlay != null )
        {
            this.overlay.setVisible(  true );
        }
        this.repaint();
    }

    public void mouseExited( MouseEvent e )
    {
        this.mouseIn = false;
        if( this.overlay != null )
        {
            this.overlay.setVisible( true );
        }
        this.repaint();
    }

    public void mouseDragged( MouseEvent e )
    {
        if( ( KeyEvent.ALT_DOWN_MASK & e.getModifiersEx() ) == KeyEvent.ALT_DOWN_MASK )
        {
//            System.out.println( "Alt held" );
            altModifier = true;
        }

        dragPoint = e.getPoint();

        if( startPoint == null )
            this.startPoint = new Point( e.getX(), e.getY() );

        if( isAtRotationHotSpot( startPoint ) )
        {
            modifyMode = ROTATE_MODE;
//            System.out.println( "Corner" );
//            System.out.println( "Rotation : " + ( Math.atan2( e.getX(), e.getY() ) * 360 / ( 2 * Math.PI ) ) );
            rotationPoint = e.getPoint();
        }
        else if( isAtResizeHotSpot( startPoint ) )
        {
            modifyMode = RESIZE_MODE;
//            System.out.println( "Resize" );
            resizePoint = whichSpot( startPoint, resizeSpots );

        }
        else
        {
            modifyMode = PAN_MODE;
            locationOffset = new Point( e.getX() - startPoint.x, e.getY() - startPoint.y  );
            if( snapToFrame )
            {
                if( Math.abs(  locationOffset.x ) < 5 )
                {
                    locationOffset.x = 0;
                }

                if( Math.abs( locationOffset.y ) < 5 )
                {
                    locationOffset.y = 0;
                }
            }
        }

        this.repaint();
    }

    public void mouseMoved( MouseEvent e )
    {
        if( trackMovement )
        {
            System.out.println( "" + e.getX() + ", " + e.getY() );
            mouseLocation = e.getPoint();
        }
    }

    public int whichSpot( Point p, Rectangle[] spots )
    {
        int result = -1;
        if( p != null && spots != null )
        {
            for( int i = 0; i < spots.length; i++ )
            {

                if( spots[ i ].contains( p ) )
                {
                    result = i;
                }
            }
        }
        return result;
    }

    public boolean isIn( Point p, Rectangle[] spots )
    {
        boolean result = false;
        if( p != null && spots != null )
        {
            for( int i = 0; i < spots.length; i++ )
            {
                result |= spots[ i ].contains( p );
            }
        }
        return result;
    }

    public boolean isAtRotationHotSpot( Point p )
    {
        return isIn( p, rotationSpots );
    }

    public boolean isAtResizeHotSpot( Point p )
    {
        return isIn( p, resizeSpots );
    }

    public boolean isSnapToFrame()
    {
        return snapToFrame;
    }

    public void setSnapToFrame( boolean snapToFrame )
    {
        this.snapToFrame = snapToFrame;
    }

    public ImageFrame getImageFrame()
    {
        return imageFrame;
    }

    public void setImageFrame( ImageFrame imageFrame )
    {
        this.imageFrame = imageFrame;
    }

    public void setLoadedImage( LoadedImage img )
    {
        this.imageFrame.setLoadedImage( img );

        if( this.imageFrame.hasTransformations() )
        {
            int option = JOptionPane.showConfirmDialog( this, "Reset changes?", "Reset", JOptionPane.OK_CANCEL_OPTION );
            switch( option )
            {
                case JOptionPane.OK_OPTION:
                    this.imageFrame.resetTransformations();
                    this.locationOffset = new Point( 0, 0 );
                    this.location = new Point( 0, 0 );
                    break;
            }
        }
        this.updateUI();
    }

    public void dragEnter( DropTargetDragEvent dtde )
    {
        System.out.println( "Drag Entered" );
        Transferable transferData = dtde.getTransferable();
        if( transferData instanceof LoadedImage )
        {
            System.out.println( "Accepted drag event from " + dtde.getSource().getClass() );
            dtde.acceptDrag( TransferHandler.LINK );
        }
    }

    public void dragOver( DropTargetDragEvent dtde )
    {
        System.out.println( "Drag Overed" );
        Transferable transferData = dtde.getTransferable();
        if( transferData instanceof LoadedImage )
        {
            System.out.println( "Accepted drag event from " + dtde.getSource().getClass() );
            dtde.acceptDrag( TransferHandler.LINK );
        }
    }

    public void dropActionChanged( DropTargetDragEvent dtde )
    {
    }

    public void dragExit( DropTargetEvent dte )
    {
    }

    public void drop( DropTargetDropEvent dtde )
    {
        try
        {
            Transferable transferable = dtde.getTransferable();
            Object transferData = transferable.getTransferData( LoadedImage.LOADED_IMAGE_DATA_FLAVOR );
            System.out.println( "Here" );
            if ( transferData instanceof LoadedImage )
            {
                dtde.acceptDrop( DnDConstants.ACTION_COPY );
                LoadedImage img = (LoadedImage) transferData;
                setLoadedImage( img );
                AffineTransform transform = new AffineTransform();
                double scaleX = getBounds().getWidth()/this.imageFrame.getLoadedImage().getSize().getWidth();
                double scaleY = getBounds().getHeight()/this.imageFrame.getLoadedImage().getSize().getHeight();
                transform.scale( scaleX, scaleY);
                this.imageFrame.applyTransformation( transform );
                this.rotationOffset = 0;
                this.rotationPoint = new Point();
                this.scalingOffset = new Point();
                this.locationOffset = new Point();
                this.repaint();
               dtde.dropComplete( true );
            }
        }
        catch ( UnsupportedFlavorException ex )
        {
            Exceptions.printStackTrace( ex );
        }
        catch ( IOException ex )
        {
            Exceptions.printStackTrace( ex );
        }
    }
}